"""Utilities to run functions with a timeout."""
import functools
import multiprocessing.pool


class FunctionTimeout(Exception):
    """Function has exceeded allowed timeout."""


def timeout(timeout_seconds):
    """Decorate a function to raise an exception on timeout."""

    def timeout_decorator(item):
        """Wrap the original function."""

        @functools.wraps(item)
        def func_wrapper(*args, **kwargs):
            """Closure for function."""
            pool = multiprocessing.pool.ThreadPool(processes=1)
            async_result = pool.apply_async(item, args, kwargs)
            try:
                return async_result.get(timeout_seconds)
            except multiprocessing.context.TimeoutError:
                raise FunctionTimeout(f'Function has exceeded '
                                      f'{timeout_seconds} s') from None

        return func_wrapper

    return timeout_decorator


def func_timeout(func, timeout_seconds, args=(), **kwargs):
    """Call function with args, raise exception on timeout."""
    return timeout(timeout_seconds)(func)(*args, **kwargs)
